﻿using System;
using System.Linq;
using System.Text;
using System.Xml;
using Windows.UI;
using Windows.UI.Xaml.Documents;
using LinkPad.Helpers;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;

namespace LinkPad.Shapes
{
    public class ManagedText : ManagedShape
    {
        #region Fields

        public readonly static Color DefaultTextColor = Colors.Black;
        public readonly static Color DefaultFillColor = Colors.Yellow;

        public const double DefaultFontSize = 16;

        private double _realX;
        private double _realY;
        private double _realWidth;
        private double _realHeight;
        private readonly double _refPtX; // 0 to 1
        private readonly double _refPtY; // 0 to 1

        private double _fontSize;

        private bool _realBoundingBoxReady;

        #endregion

        #region Constructors

        public ManagedText(ShapeManager shapeManager, double refPtX=0.5, double refPtY=0.5) : base(shapeManager)
        {
            InnerTextBlock = new RichTextBlock();
            InnerTextBlock.SizeChanged += InnerTextBlockOnSizeChanged;
            InnerTextBlock.IsTextSelectionEnabled = false;

            InnerRectangle = new Rectangle();

            _refPtX = refPtX;
            _refPtY = refPtY;

            FontSize = DefaultFontSize;
        }

        #endregion

        #region Properties

        #region ManagedShape members

        public override Rect BoundingBox
        {
            get
            {
                var rect = new Rect(Left, Top, ActualWidth, ActualHeight);
                return rect;
            }
        }

        public override Rect RealBoundingBox
        {
            get
            {
                var rect = new Rect(RealLeft, RealTop, _realWidth, _realHeight);
                return rect;
            }
        }

        public override bool RealBoundingBoxReady
        {
            get
            {
                return _realBoundingBoxReady;
            }
        }

        public RichTextBlock InnerTextBlock { get; private set; }

        public Rectangle InnerRectangle { get; private set; }

        public Brush Foreground
        {
            get { return InnerTextBlock.Foreground; }
            set { InnerTextBlock.Foreground = value; }
        }

        public Brush Fill
        {
            get { return InnerRectangle.Fill; }
            set { InnerRectangle.Fill = value; }
        }

        public Color ForegroundColor
        {
            get
            {
                var solidBrush = Foreground as SolidColorBrush;
                return solidBrush != null ? solidBrush.Color : DefaultTextColor;
            }
            set
            {
                Foreground = new SolidColorBrush(value);
            }
        }

        public Color FillColor
        {
            get
            {
                var solidBrush = Fill as SolidColorBrush;
                return solidBrush != null ? solidBrush.Color : DefaultFillColor;
            }
            set
            {
                Fill = new SolidColorBrush(value);
            }
        }

        public double Left
        {
            get { return (double) InnerTextBlock.GetValue(Canvas.LeftProperty); }
            private set
            {
                InnerTextBlock.SetValue(Canvas.LeftProperty, value);
                InnerRectangle.SetValue(Canvas.LeftProperty, value);
            }
        }

        public double Right
        {
            get { return Left + ActualWidth; }
        }

        public double Bottom
        {
            get { return Top + ActualHeight; }
        }

        public double Top
        {
            get { return (double)InnerTextBlock.GetValue(Canvas.TopProperty); }
            private set
            {
                InnerTextBlock.SetValue(Canvas.TopProperty, value);
                InnerRectangle.SetValue(Canvas.TopProperty, value);
            }
        }

        public double ActualWidth
        {
            get { return InnerRectangle.ActualWidth; }
        }

        public double ActualHeight
        {
            get { return InnerRectangle.ActualHeight; }
        }

        public double X
        {
            get { return Left + ActualWidth * _refPtX; }
            set
            {
                ShapeManager.ShapeChanged(this);
                _realX = ShapeManager.GetAbsoluteLocationX(value);
                if (Math.Abs(X - value) < double.Epsilon) return;
                Left = value - ActualWidth * _refPtX;
            }
        }

        public double Y
        {
            get { return Top + ActualHeight*_refPtY; }
            set
            {
                ShapeManager.ShapeChanged(this);
                _realY = ShapeManager.GetAbsoluteLocationY(value);
                if (Math.Abs(Y - value) < double.Epsilon) return;
                Top = value - ActualHeight * _refPtY;
            }
        }

        private double RealLeft
        {
            get { return _realX - _realWidth*_refPtX; }
        }

        private double RealTop
        {
            get { return _realY - _realHeight*_refPtY; }
        }

        #endregion

        public double FontSize
        {
            get
            {
                return _fontSize;
            }
            set
            {
                _fontSize = value;
                Transform();
            }
        }

        #endregion

        #region Methods

        #region ManagedShape members

        public override void AddToCanvas()
        {
            ShapeManager.Canvas.Children.Add(InnerRectangle);
            ShapeManager.Canvas.Children.Add(InnerTextBlock);
        }

        public override void RemoveFromCanvas()
        {
            ShapeManager.Canvas.Children.Remove(InnerTextBlock);
            ShapeManager.Canvas.Children.Remove(InnerRectangle);
        }

        public override int[] IndicesInCanvas()
        {
            var res = new int[2];
            res[0] = ShapeManager.Canvas.Children.IndexOf(InnerTextBlock);
            res[1] = res[0] + 1;
            return res;
        }

        public override void InsertToCanvas(int index)
        {
            ShapeManager.Canvas.Children.Insert(index, InnerTextBlock);
            ShapeManager.Canvas.Children.Insert(index + 1, InnerRectangle);
        }

        public override double DistanceToPoint(Point point)
        {
            var x = point.X;
            var y = point.Y;
            if (x < Left)
            {
                if (y < Top)
                {
                    return GeometryHelper.Distance(x, y, Left, Top);
                }
                if (y > Bottom)
                {
                    return GeometryHelper.Distance(x, y, Left, Bottom);
                }
                return Left - x;
            }
            if (x > Right)
            {
                if (y < Top)
                {
                    return GeometryHelper.Distance(x, y, Right, Top);
                }
                if (y > Bottom)
                {
                    return GeometryHelper.Distance(x, y, Right, Bottom);
                }
                return x - Right;
            }
            if (y < Top)
            {
                return Top - y;
            }
            if (y > Bottom)
            {
                return y - Bottom;
            }
            return 0;
        }

        public override void Transform()
        {
            var scaleX = ShapeManager.ScaleX;
            var scaleY = ShapeManager.ScaleY;
            var offsetX = ShapeManager.OffsetX;
            var offsetY = ShapeManager.OffsetY;

            // TODO change width and height accordingly as well
            InnerRectangle.Width = _realWidth*scaleX;
            InnerRectangle.Height = _realHeight*scaleY;
            InnerTextBlock.FontSize = FontSize * Math.Min(scaleX, scaleY);

            var x = _realX*scaleX + offsetX;
            var y = _realY*scaleY + offsetY;

            Left = x - ActualWidth*_refPtX;
            Top = y - ActualHeight*_refPtY;
        }

        /// <summary>
        ///  Saves the shape to the specified XML writer
        /// </summary>
        /// <param name="xmlWriter">XML writer to write the stream to</param>
        public override void WriteToXml(XmlWriter xmlWriter)
        {
            xmlWriter.WriteStartElement("Text");
            xmlWriter.WriteAttributeString("X", _realX.ToString());
            xmlWriter.WriteAttributeString("Y", _realY.ToString());
            xmlWriter.WriteAttributeString("Width", _realWidth.ToString());
            xmlWriter.WriteAttributeString("Height", _realHeight.ToString());
            xmlWriter.WriteAttributeString("Foreground", ForegroundColor.ToString());
            xmlWriter.WriteAttributeString("Fill", FillColor.ToString());
            if (InnerTextBlock.Blocks.Count > 0)
            {
                var p = (Paragraph)InnerTextBlock.Blocks[0];
                if (p.Inlines.Count > 0)
                {
                    var run = (Run)p.Inlines[0];

                    var encoding = new UTF8Encoding();
                    var bytes = encoding.GetBytes(run.Text);
                    var b64 = Convert.ToBase64String(bytes);
                    xmlWriter.WriteElementString("Content", b64);
                }
            }
            xmlWriter.WriteEndElement();
            
        }

        /// <summary>
        ///  Loads the shape from the specified XML reader
        /// </summary>
        /// <param name="xmlReader">XML reader to read the stream from</param>
        /// <returns>true if reading is successful</returns>
        public override bool ReadFromXml(XmlReader xmlReader)
        {
            var paramRead = new[] { false, false, false, false };
            var foreground = DefaultTextColor;
            var fill = DefaultFillColor;
            while (xmlReader.MoveToNextAttribute())
            {
                switch (xmlReader.Name)
                {
                    case "X":
                        _realX = Convert.ToDouble(xmlReader.Value);
                        paramRead[0] = true;
                        break;
                    case "Y":
                        _realY = Convert.ToDouble(xmlReader.Value);
                        paramRead[1] = true;
                        break;
                    case "Width":
                        _realWidth = Convert.ToDouble(xmlReader.Value);
                        paramRead[2] = true;
                        break;
                    case "Height":
                        _realHeight = Convert.ToDouble(xmlReader.Value);
                        paramRead[3] = true;
                        break;
                    case "Foreground":
                    {
                        var temp = xmlReader.Value;
                        var a = Convert.ToByte(temp.Substring(1, 2), 16);
                        var r = Convert.ToByte(temp.Substring(3, 2), 16);
                        var g = Convert.ToByte(temp.Substring(5, 2), 16);
                        var b = Convert.ToByte(temp.Substring(7, 2), 16);
                        foreground = Color.FromArgb(a, r, g, b);
                        break;
                    }
                    case "Fill":
                    {
                        var temp = xmlReader.Value;
                        var a = Convert.ToByte(temp.Substring(1, 2), 16);
                        var r = Convert.ToByte(temp.Substring(3, 2), 16);
                        var g = Convert.ToByte(temp.Substring(5, 2), 16);
                        var b = Convert.ToByte(temp.Substring(7, 2), 16);
                        fill = Color.FromArgb(a, r, g, b);
                        break;
                    }
                }
            }

            ForegroundColor = foreground;
            FillColor = fill;

            do
            {
                xmlReader.Read();
            } while (xmlReader.NodeType != XmlNodeType.Element);

            if (xmlReader.Name != "Content")
            {
                return false;
            }

            var s = xmlReader.ReadElementContentAsString();

            var bytes = Convert.FromBase64String(s);
            var encoding = new UTF8Encoding();
            var content = encoding.GetString(bytes, 0, bytes.Length);

            InnerTextBlock.Blocks.Clear();
            var paragraph = new Paragraph();
            paragraph.Inlines.Add(new Run { Text = content });
            InnerTextBlock.Blocks.Add(paragraph);

            return paramRead.All(b => b);
        }

        private void InnerTextBlockOnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            // updates inner rectangle with the text block
            // and this size which may not be transfered to inner rectangle's acutal size right away
            // is used to update real size
            InnerRectangle.Width = InnerTextBlock.ActualWidth;
            InnerRectangle.Height = InnerTextBlock.ActualHeight;
            _realWidth = ShapeManager.GetAbsoluteWidth(InnerRectangle.Width);
            _realHeight = ShapeManager.GetAbsoluteHeight(InnerRectangle.Height);

            ShapeManager.ShapeChanged(this);
            Transform();

            _realBoundingBoxReady = true;
        }

        #endregion

        #endregion
    }
}
